package solver;

import java.util.ArrayList;
import java.util.List;

import solver.moves.AbstractMove;
import solver.moves.BalanceCostEliminator;
import solver.moves.BestProcessOfMostLoadedMachineToBestMachineMove;
import solver.moves.EliminateSingleMachineCost;
import solver.moves.MachineLoadEliminator;
import solver.moves.MachineMoveCostEliminator;
import solver.moves.MathMove;
import solver.moves.MathMoveNeighborhoodXLocations;
import solver.moves.ProcessMoveEliminator;
import solver.moves.RandomMachinePairBestProcessExchangeMove;
import solver.moves.RandomMachinesRandomProcessExchangeMove;
import solver.moves.RuinAndReconstructMove;
import solver.moves.ServiceCostReducer;
import solver.moves.SingleProcessOfMostLoadedMachineToBestMachineMove;
import solver.moves.SingleRandomProcessToAllMachinesSequential;
import solver.moves.SingleRandomProcessToRandomMachineMove;
import solver.moves.SingleRandomProcessToSeqeuntialMachines;
import solver.moves.VariableNeighBourhood;
import solver.moves.WorstMachinePairBestProcessExchange;
import util.Chronograph;

import command.Invoker;
import command.MoveProcessToMachineCommand;

import model.IncrementalCostCalculator;
import model.IncrementalCostCalculatorNoBalance;
import model.IncrementalValidator;
import model.MachineSelector;
import model.SolverEnvironment;
import model.Problem;
import model.Solution;

public abstract class BaseHeuristicSolver extends BaseSolver{

	String name = "BaseHeuristicSolver";
	
	private final int MAX_NUMBER_OF_MOVED_PROCESSES_PER_MOVE = 3;
	int MAX_NO_IMPROVEMENT_ITERATIONS = 10000;
	int MAX_NO_ITERATIONS = 1000000;
	int MAX_SOLUTION_TIME = 120;

	// Moves
	SingleRandomProcessToAllMachinesSequential move1;
	SingleRandomProcessToRandomMachineMove move2;
	SingleRandomProcessToSeqeuntialMachines move3;

	RandomMachinesRandomProcessExchangeMove excMove;
	RandomMachinePairBestProcessExchangeMove excMove2;

	SingleProcessOfMostLoadedMachineToBestMachineMove cmplex1;
	BestProcessOfMostLoadedMachineToBestMachineMove cmplex2;
	WorstMachinePairBestProcessExchange cmplexExch1;
	ServiceCostReducer cmplexExch2;

	MathMove mathMove;
	MathMoveNeighborhoodXLocations mathMove2;
	
	EliminateSingleMachineCost multi1;
	RuinAndReconstructMove rrm;
	ProcessMoveEliminator pme;
	MachineLoadEliminator mle;
	BalanceCostEliminator bce;
	MachineMoveCostEliminator mme;
	
	VariableNeighBourhood vn;
	
	AbstractMove lastMove;
	
	boolean oldVerbose;
	Chronograph chrono;
	
	public BaseHeuristicSolver(Problem problem, Solution initialSolution) {
		super(problem, initialSolution);
		init();
	}

	public BaseHeuristicSolver(Problem problem, Solution initialSolution,
			Solution newSolution) {
		super(problem, initialSolution, newSolution);
		init();
	}
	
	private void init() {
		//Reset Chrono
		chrono = new Chronograph();
		chrono.reset();
		resetMoves();
	}

	protected void resetMoves() {
		vn = new VariableNeighBourhood();
		// Simple Single Process Moves
		move1 = new SingleRandomProcessToAllMachinesSequential(env);
		move2 = new SingleRandomProcessToRandomMachineMove(env);
		move3 = new SingleRandomProcessToSeqeuntialMachines(env);
		
		vn.add(move1);vn.add(move2);vn.add(move3);
		
		// Simple Exchange Moves
		excMove = new RandomMachinesRandomProcessExchangeMove(env);
		excMove2 = new RandomMachinePairBestProcessExchangeMove(env);
		
		vn.add(excMove);vn.add(excMove2);
		
		MachineSelector ms = new MachineSelector(env);
		cmplex1 = new SingleProcessOfMostLoadedMachineToBestMachineMove(env,ms);
		cmplex2 = new BestProcessOfMostLoadedMachineToBestMachineMove(env,ms);
		cmplexExch1 = new WorstMachinePairBestProcessExchange(env,ms);
		cmplexExch2 = new ServiceCostReducer(env);
		
		vn.add(cmplex1);vn.add(cmplex2);vn.add(cmplexExch1);vn.add(cmplexExch2);
		
		mathMove = new MathMove(env,ms);
		mathMove2 = new MathMoveNeighborhoodXLocations(env,ms);
		
		multi1= new EliminateSingleMachineCost(env,ms);

		vn.add(multi1);
//		rrm = new RuinAndReconstructMove(ms);
//		vn.add(rrm);
		pme = new ProcessMoveEliminator(env);
		mle = new MachineLoadEliminator(env,ms);
		bce = new BalanceCostEliminator(env,ms);
		mme = new MachineMoveCostEliminator(env);
		
		vn.add(mathMove);vn.add(mathMove2);vn.add(pme);vn.add(mle);
		vn.add(bce);vn.add(mme);
		
		lastMove = null;
	}
	
	protected void startSolution(){		
		//Reset Legality & Cost Evals
		getFullValidator().computeCost();
		getCostCalculator().computeCost();
		
		//Reset Solution Bounds
		best_cost = current_cost = getCostCalculator().getNewSolutionCost();	
		getCurrentSolution().setCost(current_cost);
		getCurrentSolution().setCostComponents(getCostCalculator().getCostComponentMemory());
		best_solution = getCurrentSolution().copy();
		

		//Reset env parameters
		oldVerbose = Problem.VERBOSE;
		Problem.VERBOSE = false;
	}

	protected boolean makeNextMove() {
//		checkConsistency();
		
		int choice = getRandom().nextInt(50000);
		
		if (choice < 28000)
			return makeNextMoveOnCurrentSingle();
		
		if((
		   (getCostCalculator().getCostComponentMemory()[0]==best_solution.getCostComponents()[0] && iterations % 999==0)
//		   ||(iterations % 28999==0)
		   ) 
		   && iterations>100000){
			lastMove=pme;
//			System.out.print("PME "+iterations+":");
//			System.out.print("cost_before:"+getCostCalculator().getNewSolutionCost());
			if (lastMove.executeMove()) {
				getCostCalculator().updateNewSolutionCost();
				potential_cost = getCostCalculator().getNewSolutionCost();
//				System.out.println("->cost_after:"+getCostCalculator().getNewSolutionCost()+" SUCCESS");
				return true;
			} else{
//				System.out.println("->cost_after:"+getCostCalculator().getNewSolutionCost()+" FAILED");
				iterations++;
				return false;
			}
	    }
		
		if((
			(getCostCalculator().getCostComponentMemory()[0]==best_solution.getCostComponents()[0] && iterations % 6999==0)
//			   ||(iterations % 28999==0)
			) 
			&& iterations>100000){
			lastMove=mme;
//			System.out.print("MME "+iterations+":");
//			System.out.print("cost_before:"+getCostCalculator().getNewSolutionCost());
			if (lastMove.executeMove()) {
				getCostCalculator().updateNewSolutionCost();
				potential_cost = getCostCalculator().getNewSolutionCost();
//				System.out.println("->cost_after:"+getCostCalculator().getNewSolutionCost()+" SUCCESS");
				return true;
			} else{
//				System.out.println("->cost_after:"+getCostCalculator().getNewSolutionCost()+" FAILED");
				iterations++;
				return false;
			}
		}
		
		if ( (getCostCalculator().getCostComponentMemory()[1]!=0) && (
				(2*getCostCalculator().getCostComponentMemory()[0]<getCostCalculator().getCostComponentMemory()[1] && iterations % 3989==0) ||
			 (iterations % 15555==0 && iterations>50000)
			 )
				){
//			if(getRandom().nextBoolean()){
//				current_cost = best_cost;
//				env.setCurrentSolution(best_solution.copy());
//			}
			lastMove=bce;
//			System.out.print("BCE "+iterations+":");
//			System.out.print("cost_before:"+getCostCalculator().getNewSolutionCost());
			if (lastMove.executeMove()) {
				getCostCalculator().updateNewSolutionCost();
				potential_cost = getCostCalculator().getNewSolutionCost();
//				System.out.println("->cost_after:"+getCostCalculator().getNewSolutionCost()+" SUCCESS");
				return true;
			} else{
//				System.out.println("->cost_after:"+getCostCalculator().getNewSolutionCost()+" FAILED");
				iterations++;
				return false;
			}
		}
		
		if ((getCostCalculator().getCostComponentMemory()[0]!=0) && iterations % 3999==0 && iterations>50000){
//			if(getRandom().nextBoolean()){
//				current_cost = best_cost;
//				env.setCurrentSolution(best_solution.copy());
//			}
			lastMove=mle;
//			System.out.print("MLE "+iterations+":");
//			System.out.print("cost_before:"+getCostCalculator().getNewSolutionCost());
			if (lastMove.executeMove()) {
				getCostCalculator().updateNewSolutionCost();
				potential_cost = getCostCalculator().getNewSolutionCost();
//				System.out.println("->cost_after:"+getCostCalculator().getNewSolutionCost()+" SUCCESS");
				return true;
			} else{
//				System.out.println("->cost_after:"+getCostCalculator().getNewSolutionCost()+" FAILED");
				iterations++;
				return false;
			}
		}
		
		if (iterations % 91000==0 && iterations>20000){
			if(getRandom().nextBoolean()){
				current_cost = best_cost;
				env.setCurrentSolution(best_solution.copy());
				return true;
			}
			else{
				iterations++;
				return false;
			}
		}
		
//		if (iterations % 42000==0 && iterations>500000){
//			if(getRandom().nextBoolean()){
//				current_cost = best_cost;
//				env.setCurrentSolution(best_solution.copy());
//			}
//			lastMove=mathMove;
////			System.out.print(""+iterations+":");
//			if (lastMove.executeMove()) {
//				getCostCalculator().computeCost();
//				potential_cost = getCostCalculator().getNewSolutionCost();
//				return true;
//			} else{
//				iterations++;
//				return false;
//			}
//		}
//		
//		if (iterations % 43000==0 && iterations>420000){
//			if(getRandom().nextBoolean()){
//				current_cost = best_cost;
//				env.setCurrentSolution(best_solution.copy());
//			}
//			lastMove=mathMove2;
//					int n = env.getRandom().nextInt(env.getProblem().N);
////			int n = env.getProblem().getNeighborhoodIdForMachineId(mathMove2.ms.selectMostLoadedMachine(2));
//			mathMove2.setSelectedNeighborhood(n);
//			//		System.out.print(""+iterations+":");
//			if (lastMove.executeMove()) {
//				getCostCalculator().computeCost();
//				potential_cost = getCostCalculator().getNewSolutionCost();
//				return true;
//			} else{
//				iterations++;
//				return false;
//			}
//		}
		
////		if (iterations % 80000==0 && iterations>200000){
////			
////			lastMove=multi1;
////			System.out.print("MCE "+iterations+":");
////			System.out.print("cost_before:"+getCostCalculator().getNewSolutionCost());
////			
////			if (lastMove.executeMove()) {
////				getCostCalculator().updateNewSolutionCost();
////				potential_cost = getCostCalculator().getNewSolutionCost();
////				System.out.println("->cost_after:"+getCostCalculator().getNewSolutionCost()+" SUCCESS");
////				return true;
////			} else{
////				System.out.println("->cost_after:"+getCostCalculator().getNewSolutionCost()+" FAILED");
////				iterations++;
////				return false;
////			}
////		}
//		
		return makeNextMoveOnCurrentExchange();	
//		return makeNextMoveOnCurrentSingle();
	}

	private void checkConsistency() {
		if(current_cost!=getCostCalculator().getNewSolutionCost()){
			System.out.println("Problem in solution cost consistency with icc");
			System.exit(-1);
		}
		getFullValidator().computeCost();
		long ece_cost = getFullValidator().getNewSolutionCost();
		if(ece_cost!=getCostCalculator().getNewSolutionCost()){
			System.out.println("Problem in solution cost consistency ece="+ece_cost+
					" with icc="+getCostCalculator().getNewSolutionCost());
			System.exit(-1);
		}
	}

	private boolean makeNextMoveOnCurrentSingle() {
//		getCostCalculator().saveCostComponentsState();
		if (makeSingleRandomMove()) {
			getCostCalculator().updateNewSolutionCost();
			potential_cost = getCostCalculator().getNewSolutionCost();
			// cc.computeCost();
			// assert(cc.getNewSolutionCost()==potential_cost);
			return true;
		} else {
			undo();
			return false;
		}
	}
	
	private boolean makeNextMoveOnCurrentExchange() {
//		getCostCalculator().saveCostComponentsState();
		if (makeExchangeMoveOnCurrent()) {
			getCostCalculator().updateNewSolutionCost();
			potential_cost = getCostCalculator().getNewSolutionCost();
			// cc.computeCost();
			// assert(cc.getNewSolutionCost()==potential_cost);
			return true;
		} else {
			undo();
			return false;
		}
	}

//	private boolean makeNextMoveOnCurrent() {
//		if (!makeRandomMoves(getRandom()
//				.nextInt(MAX_NUMBER_OF_MOVED_PROCESSES_PER_MOVE) + 1)) {
//			return false;
//		}
//
//		// if (iv.isFeasible()) {
//		// cc.computeCost();
//		// potential_cost = cc.getNewSolutionCost();
//		return true;
//		// }
//		// else{
//		// invoker.undoAllCommands();
//		// return false;
//		// }
//	}

	public boolean makeSingleRandomMove() {
		switch (getRandom().nextInt(10)) {
		case 0:
		case 1:
		case 2:
		case 3:
		case 4:
			if (getRandom().nextInt(10) < 5)
				lastMove = move1;
			else
				lastMove = move3;
			break;
		case 5:
		case 6:
		case 7:
			lastMove = move2;
			break;
		case 8:
			lastMove = cmplex1;
			break;
		case 9:
			lastMove = cmplex2;
		}

		return lastMove.executeMove();
	}

	private boolean makeExchangeMoveOnCurrent() {

		switch (getRandom().nextInt(10)) {
		case 0:
		case 1:
			
		case 2:
		case 3:
		case 4:
			lastMove = excMove;
			break;
		case 5:
		case 6:

		case 7:
			lastMove = excMove2;
			break;
		case 8:
			lastMove = cmplexExch1;
			break;
		case 9:
			lastMove = cmplexExch2;
			break;
		}

		if (lastMove.executeMove()) {
			getCostCalculator().updateNewSolutionCost();
			potential_cost = getCostCalculator().getNewSolutionCost();
			return true;
		} else
			return false;
	}
	
	protected void finishIteration(boolean accept, boolean resetImprovement){
		if(accept){
			current_cost = potential_cost;
			commit();
		}
		else{
			undo();
		}
		
		if(resetImprovement)
			noImprove = 0;
		else
			noImprove++;
		noBestImprove++;
	}
	
	protected void commit() {
		lastMove.commit();
	}

	protected void undo() {
		lastMove.undo();
	}

	private String printCostAnalysis() {
		System.out.println("Last move:"
				+ getInvoker().getCommands().get(0).toString());
		System.out.println("Solution status:" + getFullValidator().isFeasible());
		System.out.println("Old machine balance cost = "
				+ getCostCalculator().getBalanceCost(getCurrentSolution(), 0));
		System.out.println("New machine balance cost = "
				+ getCostCalculator().getBalanceCost(getCurrentSolution(), 1));
		getFullValidator().printNewSolutionCostAnalysis("ece");
		getCostCalculator().printNewSolutionCostAnalysis("cc");
		return "";
	}


//	@Deprecated
//	private boolean makeNextMoveWithCopy() {
//		// This is inefficient
//		setPotentialSolution(getCurrentSolution().copy());
//		invoker = new Invoker();
//
//		getIncrementalValidator().setSolutionPair(getInitialSolution(), getPotentialSolution());
//
//		if (!makeRandomMoves(getRandom()
//				.nextInt(MAX_NUMBER_OF_MOVED_PROCESSES_PER_MOVE) + 1))
//			return false;
//
//		if (getIncrementalValidator().isFeasible()) {
//			getCostCalculator().computeCost();
//			potential_cost = getFullValidator().getNewSolutionCost();
//			return true;
//		} else
//			return false;
//	}

	public Solution getNewSolution() {
		return getCurrentSolution();
	}

	public boolean makeRandomMoves(int numberOfProcesses) {
		int P_SIZE = numberOfProcesses;
		List<Integer> selectedProcesses = new ArrayList<Integer>();
		if (getProblem().P < P_SIZE) {
			P_SIZE = getProblem().P;
		}
		// Select a random set of processes
		while (selectedProcesses.size() < P_SIZE) {
			int x = getRandom().nextInt(getProblem().P);
			if (!selectedProcesses.contains(x))
				selectedProcesses.add(x);
		}

		boolean ret = true;
		getCostCalculator().saveCostComponentsState();
		// Assign them to a machine
		for (Integer p_index : selectedProcesses) {
			ret = ret && move1.performMove(p_index);
			if (ret == false) {
				getInvoker().undoAllCommands();
				getCostCalculator().restoreCostComponentsState();
				break;
			} else
				getCostCalculator().updateNewSolutionCost();
		}
		potential_cost = getCostCalculator().getNewSolutionCost();
		return ret;
	}

	@Deprecated
	public boolean testAndUndoWithSchedule(int p_index, int m_index) {
		int orig_m_index = getCurrentSolution().getMachineIdForProcessId(p_index);
		MoveProcessToMachineCommand cmd = new MoveProcessToMachineCommand(getFullValidator());
		cmd.setP_id(p_index);
		cmd.setM_id(m_index);
		getInvoker().addCommand(cmd);
		getInvoker().executeLastCommand();

		// Make a fast check
		if (!getIncrementalValidator().isProbablyFeasible(p_index, orig_m_index, m_index)) {
			getInvoker().undoLastCommand();
			return false;
		}
		if (!getIncrementalValidator().isFeasible()) {
			getInvoker().undoLastCommand();
			return false;
		} else
			return true;
	}
	
	protected void finishSolution(boolean saveSolution){
		setCurrentSolution(best_solution.copy());
		// System.out.println("############################################");
		// System.out.println("Iterations:" + iterations);
        // getFullValidator().computeCost();
		// ece.printCostAnalysis();
		// System.out.println("Best solution cost = " + best_cost);
		
		Problem.VERBOSE = oldVerbose;
		
		
		
		//TODO: Create the save solution logic
	}
	
	protected boolean shouldStop(){
		return shouldStopTime();
	}
	
	private boolean shouldStopIterations(){
		return iterations++<MAX_NO_ITERATIONS;
	}
	
	private boolean shouldStopTime(){
		chrono.lap();
		return chrono.getElapsedTimeInSeconds() < MAX_SOLUTION_TIME;
	}
	
	public String getName() {
		return name;
	}
	
	protected IncrementalValidator getIncrementalValidator() {
		return env.getValidator();
	}

	protected IncrementalCostCalculator getCostCalculator() {
		return env.getCostCalculator();
	}

	Invoker getInvoker() {
		return env.getInvoker();
	}

}
